/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.drm.mobile1;

import java.io.*;

/**
 * This class provides interfaces to access the DRM raw content.
 */
public class DrmRawContent {
	/**
	 * The "application/vnd.oma.drm.message" mime type.
	 */
	public static final String DRM_MIMETYPE_MESSAGE_STRING = "application/vnd.oma.drm.message";

	/**
	 * The "application/vnd.oma.drm.content" mime type.
	 */
	public static final String DRM_MIMETYPE_CONTENT_STRING = "application/vnd.oma.drm.content";

	/**
	 * The DRM delivery type: Forward-Lock
	 */
	public static final int DRM_FORWARD_LOCK = 1;

	/**
	 * The DRM delivery type: Combined Delivery
	 */
	public static final int DRM_COMBINED_DELIVERY = 2;

	/**
	 * The DRM delivery type: Separate Delivery
	 */
	public static final int DRM_SEPARATE_DELIVERY = 3;

	/**
	 * The DRM delivery type: Separate Delivery in DRM message
	 */
	public static final int DRM_SEPARATE_DELIVERY_DM = 4;

	/**
	 * The DRM media content length is unknown currently
	 */
	public static final int DRM_UNKNOWN_DATA_LEN = -1;

	/**
	 * The id of "application/vnd.oma.drm.message" mime type.
	 */
	private static final int DRM_MIMETYPE_MESSAGE = 1;

	/**
	 * The id of "application/vnd.oma.drm.content" mime type.
	 */
	private static final int DRM_MIMETYPE_CONTENT = 2;

	/**
	 * Successful operation.
	 */
	private static final int JNI_DRM_SUCCESS = 0;

	/**
	 * General failure.
	 */
	private static final int JNI_DRM_FAILURE = -1;

	/**
	 * Indicates the end of the DRM content is reached.
	 */
	private static final int JNI_DRM_EOF = -2;

	/**
	 * The media content length is unknown from native method
	 */
	private static final int JNI_DRM_UNKNOWN_DATA_LEN = -3;

	/**
	 * The member to save the original InputStream data.
	 */
	private BufferedInputStream inData;

	/**
	 * The member to save the original InputStream data length.
	 */
	private int inDataLen;

	/**
	 * The unique id to this DRM content. It will be initialized in constructor
	 * by native method. And it will not be changed after initialization.
	 */
	private int id;

	/**
	 * The rights issuer address of this DRM object.
	 */
	private String rightsIssuer;

	/**
	 * The media content type of this DRM object.
	 */
	private String mediaType;

	/**
	 * The delivery method type of this DRM object.
	 */
	private int rawType;

	/**
	 * Construct a DrmRawContent object.
	 * 
	 * @param inRawdata
	 *            object of DRM raw data stream.
	 * @param len
	 *            the length of raw data can be read.
	 * @param mimeTypeStr
	 *            the mime type of the DRM content.
	 */
	public DrmRawContent(InputStream inRawdata, int len, String mimeTypeStr) throws DrmException, IOException {
		int mimeType;

		id = -1;
		inData = new BufferedInputStream(inRawdata, 1024);
		inDataLen = len;

		if (DRM_MIMETYPE_MESSAGE_STRING.equals(mimeTypeStr))
			mimeType = DRM_MIMETYPE_MESSAGE;
		else if (DRM_MIMETYPE_CONTENT_STRING.equals(mimeTypeStr))
			mimeType = DRM_MIMETYPE_CONTENT;
		else
			throw new IllegalArgumentException("mimeType must be DRM_MIMETYPE_MESSAGE or DRM_MIMETYPE_CONTENT");

		if (len <= 0)
			throw new IllegalArgumentException("len must be > 0");

		/* call native method to initialize this DRM content */
		id = nativeConstructDrmContent(inData, inDataLen, mimeType);

		if (JNI_DRM_FAILURE == id)
			throw new DrmException("nativeConstructDrmContent() returned JNI_DRM_FAILURE");

		/* init the rights issuer field. */
		rightsIssuer = nativeGetRightsAddress();

		/* init the raw content type. */
		rawType = nativeGetDeliveryMethod();
		if (JNI_DRM_FAILURE == rawType)
			throw new DrmException("nativeGetDeliveryMethod() returned JNI_DRM_FAILURE");

		/* init the media content type. */
		mediaType = nativeGetContentType();
		if (null == mediaType)
			throw new DrmException("nativeGetContentType() returned null");
	}

	/**
	 * Get rights address from raw Seperate Delivery content.
	 * 
	 * @return the string of the rights issuer address, or null if no rights
	 *         issuer.
	 */
	public String getRightsAddress() {
		return rightsIssuer;
	}

	/**
	 * Get the type of the raw DRM content.
	 * 
	 * @return one of the following delivery type of this DRM content:
	 *         #DRM_FORWARD_LOCK #DRM_COMBINED_DELIVERY #DRM_SEPARATE_DELIVERY
	 *         #DRM_SEPARATE_DELIVERY_DM
	 */
	public int getRawType() {
		return rawType;
	}

	/**
	 * Get one InputStream object to read decrypted content.
	 * 
	 * @param rights
	 *            the rights object contain decrypted key.
	 * 
	 * @return the InputStream object of decrypted media content.
	 */
	public InputStream getContentInputStream(DrmRights rights) {
		if (null == rights)
			throw new NullPointerException();

		return new DrmInputStream(rights);
	}

	/**
	 * Get the type of the decrypted media content.
	 * 
	 * @return the decrypted media content type of this DRM content.
	 */
	public String getContentType() {
		return mediaType;
	}

	/**
	 * Get the length of the decrypted media content.
	 * 
	 * @param rights
	 *            the rights object contain decrypted key.
	 * 
	 * @return the length of the decrypted media content. #DRM_UNKNOWN_DATA_LEN
	 *         if the length is unknown currently.
	 */
	public int getContentLength(DrmRights rights) throws DrmException {
		/**
		 * Because currently the media object associate with rights object has
		 * been handled in native logic, so here it is not need to deal the
		 * rights. But for the apps, it is mandatory for user to get the rights
		 * object before get the media content length.
		 */
		if (null == rights)
			throw new NullPointerException();

		int mediaLen = nativeGetContentLength();

		if (JNI_DRM_FAILURE == mediaLen)
			throw new DrmException("nativeGetContentLength() returned JNI_DRM_FAILURE");

		if (JNI_DRM_UNKNOWN_DATA_LEN == mediaLen)
			return DRM_UNKNOWN_DATA_LEN;

		return mediaLen;
	}

	/**
	 * This class provide a InputStream to the DRM media content.
	 */
	class DrmInputStream extends InputStream {
		/**
		 * The flag to indicate whether this stream is closed or not.
		 */
		private boolean isClosed;

		/**
		 * The offset of this DRM content to be reset.
		 */
		private int offset;

		/**
		 * A byte of data to be readed.
		 */
		private byte[] b;

		/**
		 * Construct a DrmInputStream instance.
		 */
		public DrmInputStream(DrmRights rights) {
			/**
			 * Because currently the media object associate with rights object
			 * has been handled in native logic, so here it is not need to deal
			 * the rights. But for the apps, it is mandatory for user to get the
			 * rights object before get the media content data.
			 */

			isClosed = false;
			offset = 0;
			b = new byte[1];
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#available()
		 */
		public int available() throws IOException {
			/* call native method to get this DRM decrypted media content length */
			int len = nativeGetContentLength();

			if (JNI_DRM_FAILURE == len)
				throw new IOException();

			/* if the length is unknown, just return 0 for available value */
			if (JNI_DRM_UNKNOWN_DATA_LEN == len)
				return 0;

			int availableLen = len - offset;
			if (availableLen < 0)
				throw new IOException();

			return availableLen;
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#read()
		 */
		public int read() throws IOException {
			int res;

			res = read(b, 0, 1);

			if (-1 == res)
				return -1;

			return b[0] & 0xff;
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#read(byte)
		 */
		public int read(byte[] b) throws IOException {
			return read(b, 0, b.length);
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#read(byte, int, int)
		 */
		public int read(byte[] b, int off, int len) throws IOException {
			if (null == b)
				throw new NullPointerException();
			if (off < 0 || len < 0 || off + len > b.length)
				throw new IndexOutOfBoundsException();
			if (true == isClosed)
				throw new IOException();

			if (0 == len)
				return 0;

			len = nativeReadContent(b, off, len, offset);

			if (JNI_DRM_FAILURE == len)
				throw new IOException();
			else if (JNI_DRM_EOF == len)
				return -1;

			offset += len;

			return len;
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#markSupported()
		 */
		public boolean markSupported() {
			return false;
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#mark(int)
		 */
		public void mark(int readlimit) {
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#reset()
		 */
		public void reset() throws IOException {
			throw new IOException();
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#skip()
		 */
		public long skip(long n) throws IOException {
			return 0;
		}

		/*
		 * Non-javadoc
		 * 
		 * @see java.io.InputStream#close()
		 */
		public void close() {
			isClosed = true;
		}
	}

	/**
	 * native method: construct a DRM content according the mime type.
	 * 
	 * @param data
	 *            input DRM content data to be parsed.
	 * @param len
	 *            the length of the data.
	 * @param mimeType
	 *            the mime type of this DRM content. the value of this field
	 *            includes: #DRM_MIMETYPE_MESSAGE #DRM_MIMETYPE_CONTENT
	 * 
	 * @return #the id of the DRM content if succeed. #JNI_DRM_FAILURE if fail.
	 */
	private native int nativeConstructDrmContent(InputStream data, int len, int mimeType);

	/**
	 * native method: get this DRM content rights issuer.
	 * 
	 * @return the address of rights issuer if in case of separate delivery.
	 *         null if not separete delivery, or otherwise.
	 */
	private native String nativeGetRightsAddress();

	/**
	 * native method: get this DRM content delivery type.
	 * 
	 * @return the delivery method, the value may be one of the following:
	 *         #DRM_FORWARD_LOCK #DRM_COMBINED_DELIVERY #DRM_SEPARATE_DELIVERY
	 *         #DRM_SEPARATE_DELIVERY_DM #JNI_DRM_FAILURE if fail.
	 */
	private native int nativeGetDeliveryMethod();

	/**
	 * native method: get a piece of media content data.
	 * 
	 * @param buf
	 *            the buffer to save DRM media content data.
	 * @param bufOff
	 *            the offset of the buffer to start to save data.
	 * @param len
	 *            the number of byte to read.
	 * @param mediaOff
	 *            the offset of the media content data to start to read.
	 * 
	 * @return the length of the media content data has been read. #JNI_DRM_EOF
	 *         if reach to end of the media content. #JNI_DRM_FAILURE if fail.
	 */
	private native int nativeReadContent(byte[] buf, int bufOff, int len, int mediaOff);

	/**
	 * native method: get this DRM content type.
	 * 
	 * @return the decrypted media content type. null if fail.
	 */
	private native String nativeGetContentType();

	/**
	 * native method: get this DRM decrypted media content length.
	 * 
	 * @return the length of decrypted media content. #JNI_DRM_FAILURE if fail.
	 *         #JNI_DRM_UNKNOWN_DATA_LEN if the length is unknown currently.
	 */
	private native int nativeGetContentLength();

	/**
	 * The finalizer of the DRMRawContent. Do some cleanup.
	 */
	protected native void finalize();

	/**
	 * Load the shared library to link the native methods.
	 */
	static {
		try {
			System.loadLibrary("drm1_jni");
		} catch (UnsatisfiedLinkError ule) {
			System.err.println("WARNING: Could not load libdrm1_jni.so");
		}
	}
}